package com.yunji.storage.business;

import com.yunji.base.cache.BaseCache;
import com.yunji.base.enums.SystemName;
import com.yunji.common.base.BaseBusiness;
import com.yunji.common.enums.business.StorageStatus;
import com.yunji.common.enums.business.StorageType;
import com.yunji.common.enums.common.ErrorEnum;
import com.yunji.common.model.dto.LoginInfo;
import com.yunji.common.utils.DecimalUtils;
import com.yunji.common.utils.TimeUtils;
import com.yunji.common.utils.bean.BeanUtils;
import com.yunji.common.utils.uuid.UUIDUtils;
import com.yunji.storage.bo.AllocateInfoListQueryBO;
import com.yunji.storage.bo.AllocateListQueryBO;
import com.yunji.storage.bo.AllocateRemoveBO;
import com.yunji.storage.bo.StockSelectQueryBO;
import com.yunji.storage.domain.*;
import com.yunji.storage.dto.*;
import com.yunji.storage.enums.AllocateStatus;
import com.yunji.storage.enums.DeletableStatus;
import com.yunji.storage.enums.DraftStatus;
import com.yunji.storage.enums.StorageBusinessType;
import com.yunji.storage.service.*;
import com.yunji.storage.vo.AllocateTicketVO;
import com.yunji.system.service.TokenService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * @Project: yunji-coffee
 * @Package: com.yunji.storage.business
 * @Title: AllocateTicketBusiness
 * <p>
 * History:
 * Date                     Version     Author          Summary
 * ============================================================
 * 2024-08-20 15:10:42      V1.0        HuaAo       新建类
 */
@Service
public class AllocateTicketBusiness extends BaseBusiness {

    @Resource
    IAllocateTicketService allocateTicketService;

    @Resource
    IAllocateInfoService allocateInfoService;

    @Resource
    IStorageTicketService storageTicketService;

    @Resource
    IStorageRecordsService storageRecordsService;

    @Resource
    IStockService stockService;

    @Resource
    StorageInteractBusiness interactBusiness;

    @Resource
    TokenService tokenService;

    /**
     * 新增调拨单
     *
     * @param dto
     */
    @Transactional
    public Long create(AllocateTicketDTO dto) {
        logs.info("【新增调拨单】调拨单信息:{}", dto);
        checkAllocateTicket(dto);

        LoginInfo user = tokenService.getUser();
        Long belongId = user.getBelongId();
        Assert.isTrue(BeanUtils.isNotNull(belongId), ErrorEnum.BUSINESS_121003.getText());
        logs.info("【新增调拨单】当前用户所属ID:{}", belongId);

        AllocateTicketDO allocateTicket = new AllocateTicketDO();
        BeanUtils.clone(dto, allocateTicket);
        allocateTicket.setBelongId(belongId);

        String allocateCode = UUIDUtils.allocateCode();
        logs.info("【新增调拨单】调拨单号:{}", allocateCode);
        allocateTicket.setAllocateStatus(AllocateStatus.WAIT_RECEIVE.getCode());
        Date time = TimeUtils.getNowDate();
        logs.info("【新增调拨单】提交时间:{}", time);
        allocateTicket.setApplyTime(time);


        allocateTicket.setCreateTime(time);
        allocateTicket.setAllocateCode(allocateCode);
        Assert.isTrue(allocateTicketService.create(allocateTicket), "新增失败");


        //处理调拨详情
        handleInfoList(allocateTicket, dto.getInfoList(), true);

        //处理出库单据
        handleStorageTicket(allocateTicket, dto.getInfoList(), StorageType.OUT_STORAGE.getCode());
        //处理入库单据
        handleStorageTicket(allocateTicket, dto.getInfoList(), StorageType.INTO_STORAGE.getCode());

        return allocateTicket.getAllocateId();
    }

    /**
     * 保存草稿
     *
     * @param dto
     */
    @Transactional
    public void saveDraft(AllocateTicketDTO dto) {
        logs.info("【保存草稿】草稿信息:{}", dto);
        if (BeanUtils.isNotNull(dto.getOutBelongId()) && BeanUtils.isNotNull(dto.getInfoList())) {
            Assert.isTrue(!Objects.equals(dto.getOutBelongId(), dto.getIntoBelongId()), ErrorEnum.CHECKED_811027.getText());
        }
        LoginInfo user = tokenService.getUser();
        Long belongId = user.getBelongId();
        Assert.isTrue(BeanUtils.isNotNull(belongId), ErrorEnum.BUSINESS_121003.getText());
        logs.info("【保存草稿】当前用户所属ID:{}", belongId);

        AllocateTicketDO allocateTicket = new AllocateTicketDO();
        BeanUtils.clone(dto, allocateTicket);
        allocateTicket.setBelongId(belongId);
        String allocateCode = UUIDUtils.allocateCode();
        logs.info("【保存草稿】调拨单号:{}", allocateCode);

        // 删除之前的草稿
        allocateTicketService.deleteByTicket(new AllocateRemoveBO(belongId, AllocateStatus.DRAFT.getCode()));

        allocateTicket.setAllocateStatus(AllocateStatus.DRAFT.getCode());
        allocateTicket.setAllocateCode(allocateCode);
        Assert.isTrue(allocateTicketService.create(allocateTicket), "新增失败");


        Long allocateId = allocateTicket.getAllocateId();
        logs.info("【保存草稿】调拨单ID:{}", allocateId);

        //处理调拨草稿详情
        if (BeanUtils.isNotNull(dto.getInfoList())) {
            handleInfoList(allocateTicket, dto.getInfoList(), false);
        }
    }

    /**
     * 提交调拨单
     *
     * @param dto
     */
    @Transactional
    public void commit(AllocateTicketDTO dto) {
        //有ID 走提交流程
        logs.info("【重新提交调拨单】调拨单信息:{}", dto);
        checkAllocateTicket(dto);

        Assert.isTrue(BeanUtils.isNotNull(dto.getAllocateId()), "未找到调拨单ID");

        Integer allocateStatus = dto.getAllocateStatus();
        Assert.isTrue(Objects.equals(AllocateStatus.DRAFT.getCode(), allocateStatus) || Objects.equals(AllocateStatus.CANCEL.getCode(), allocateStatus), "当前状态不可提交");
        logs.info("【重新提交调拨单】调拨单状态:{}", allocateStatus);

        LoginInfo user = tokenService.getUser();
        Long belongId = user.getBelongId();
        Assert.isTrue(BeanUtils.isNotNull(belongId), ErrorEnum.BUSINESS_121003.getText());
        logs.info("【重新提交调拨单】当前用户所属ID:{}", belongId);

        AllocateTicketDO allocateTicket = new AllocateTicketDO();
        BeanUtils.clone(dto, allocateTicket);
        allocateTicket.setBelongId(belongId);

        String allocateCode = UUIDUtils.allocateCode();
        logs.info("【重新提交调拨单】调拨单号:{}", allocateCode);
        allocateTicket.setAllocateStatus(AllocateStatus.WAIT_RECEIVE.getCode());
        Date time = TimeUtils.getNowDate();
        logs.info("【重新提交调拨单】提交时间:{}", time);
        allocateTicket.setApplyTime(time);

        if (Objects.equals(AllocateStatus.DRAFT.getCode(), allocateStatus)) {
            Assert.isTrue(allocateTicketService.update(allocateTicket), "修改失败");
            //处理调拨详情
            handleInfoList(allocateTicket, dto.getInfoList(), true);
        } else if (Objects.equals(AllocateStatus.CANCEL.getCode(), allocateStatus)) {
            //处理取消状态调拨单
            allocateTicket.setCreateTime(time);
            allocateTicket.setAllocateCode(allocateCode);
            handleCancelTicket(dto, allocateTicket);
        }

        //处理出库单据
        handleStorageTicket(allocateTicket, dto.getInfoList(), StorageType.OUT_STORAGE.getCode());
        //处理入库单据
        handleStorageTicket(allocateTicket, dto.getInfoList(), StorageType.INTO_STORAGE.getCode());
    }

    /**
     * 获取调拨单详情
     *
     * @param allocateId
     * @return
     */
    public AllocateTicketVO select(Long allocateId) {
        AllocateTicketVO vo = new AllocateTicketVO();
        AllocateTicketDO ticket = allocateTicketService.select(allocateId);
        BeanUtils.clone(ticket, vo);
        vo.setOutBelongName(BaseCache.getBelongName(vo.getOutBelongId()));
        vo.setIntoBelongName(BaseCache.getBelongName(vo.getIntoBelongId()));
        vo.setParentId(BaseCache.getBelong(vo.getBelongId()).getParentId());
        List<AllocateInfoDO> infoList = allocateInfoService.search(new AllocateInfoListQueryBO(allocateId));

        vo.setInfoList(infoList);
        return vo;
    }

    /**
     * 获取草稿
     *
     * @param belongId
     * @return
     */
    public AllocateTicketVO getDraft(Long belongId) {
        List<AllocateTicketVO> ticketVOList = allocateTicketService.searchVOList(new AllocateListQueryBO(belongId, AllocateStatus.DRAFT.getCode()));
        AllocateTicketVO vo = BeanUtils.isNotNull(ticketVOList) ? ticketVOList.get(0) : new AllocateTicketVO();

        Long allocateId = vo.getAllocateId();
        logs.info("【获取草稿】调拨单ID:{}", allocateId);

        if (BeanUtils.isNotNull(allocateId)) {
            List<AllocateInfoDO> infoList = allocateInfoService.search(new AllocateInfoListQueryBO(allocateId));
            vo.setInfoList(infoList);
            vo.setDraftStatus(DraftStatus.EXIST.getCode());
        } else {
            vo.setDraftStatus(DraftStatus.ABSENT.getCode());
        }

        return vo;
    }

    /**
     * 取消调拨单
     *
     * @param allocateId
     */
    @Transactional
    public void cancel(Long allocateId) {
        //取消调拨 库存需要返还 除了已完成状态 都能取消

        AllocateTicketDO vo = allocateTicketService.select(allocateId);
        Assert.isTrue(BeanUtils.isNotNull(vo), "未找到调拨单");

        Integer allocateStatus = vo.getAllocateStatus();
        logs.info("【取消调拨单】调拨单状态:{}", allocateStatus);

        Assert.isTrue(Objects.equals(AllocateStatus.WAIT_OUT_STORAGE.getCode(), allocateStatus) || Objects.equals(AllocateStatus.WAIT_RECEIVE.getCode(), allocateStatus), "当前状态不能取消调拨");

        AllocateTicketDO allocateTicket = new AllocateTicketDO();
        allocateTicket.setAllocateId(allocateId);
        allocateTicket.setAllocateStatus(AllocateStatus.CANCEL.getCode());
        Assert.isTrue(allocateTicketService.update(allocateTicket), "取消失败");

        RollbackDTO dto = new RollbackDTO();
        dto.setBusinessType(StorageBusinessType.ALLOCATE_BUSINESS.getCode());
        dto.setBusinessCode(vo.getAllocateCode());
        dto.setBusinessStatus(AllocateStatus.CANCEL.getCode());
        interactBusiness.rollback(dto);
    }

    /**
     * 批量删除调拨单
     *
     * @param allocateIds
     */
    @Transactional
    public void delete(Long... allocateIds) {
        allocateTicketService.deleteDraft(allocateIds);
        allocateInfoService.deleteByAllocateIds(allocateIds);
    }

    /*-------------------------------------------------处理/参数校验-------------------------------------------------*/

    /**
     * 处理调拨单详情
     *
     * @param infoList
     */
    private void handleInfoList(AllocateTicketDO allocateTicket, List<AllocateInfoDTO> infoList, boolean checkStatus) {
        //先查出来所有  没有就新增 有就修改  其他删除
        List<AllocateInfoDO> list = allocateInfoService.search(new AllocateInfoListQueryBO(allocateTicket.getAllocateId()));
        logs.info("【处理调拨单详情】存在的详情:{}", list);

        List<AllocateInfoDO> createList = new ArrayList<>();
        List<AllocateInfoDO> updateList = new ArrayList<>();
        List<Long> deleteIds = new ArrayList<>();

        //获取当前详情
        for (AllocateInfoDO allocateInfo : list) {
            deleteIds.add(allocateInfo.getInfoId());
        }

        //处理详情
        for (AllocateInfoDTO allocateInfoDTO : infoList) {
            AllocateInfoDO allocateInfo = new AllocateInfoDO();
            BeanUtils.clone(allocateInfoDTO, allocateInfo);
            allocateInfo.setBelongId(allocateTicket.getBelongId());
            allocateInfo.setAllocateId(allocateTicket.getAllocateId());
            allocateInfo.setAllocateCode(allocateTicket.getAllocateCode());

            //非草稿状态都需要检验
            if (checkStatus) {
                checkAllocateInfo(allocateInfoDTO);
                checkStock(allocateTicket, allocateInfo);
            }
            //进行修改的详情
            Long infoId = allocateInfo.getInfoId();
            logs.info("【处理调拨单详情】详情ID:{}", infoId);
            if (BeanUtils.isNotNull(allocateInfo.getInfoId())) {
                //进行修改的剔除删除行列
                updateList.add(allocateInfo);
                if (deleteIds.contains(infoId)) {
                    deleteIds.remove(infoId);
                }
            } else {
                createList.add(allocateInfo);
            }
        }

        logs.info("【处理调拨单详情】删除详情Ids:{}", deleteIds);
        for (Long deleteId : deleteIds) {
            Assert.isTrue(allocateInfoService.delete(deleteId), "删除失败");
        }

        logs.info("【处理调拨单详情】新增调拨单详情:{}", createList);
        for (AllocateInfoDO create : createList) {
            Assert.isTrue(allocateInfoService.create(create), "新增失败");
        }

        logs.info("【处理调拨单详情】修改调拨单详情:{}", updateList);
        for (AllocateInfoDO update : updateList) {
            allocateInfoService.update(update);
        }

    }

    /**
     * 处理出入库单据
     */
    private void handleStorageTicket(AllocateTicketDO allocateTicket, List<AllocateInfoDTO> infoList, Integer storageType) {
        //业务单独处理  没有动库存

        StorageTicketDO storageTicket = new StorageTicketDO();
        List<StorageRecordsDO> recordsList = new ArrayList<>();

        Long allocateId = allocateTicket.getAllocateId();
        logs.info("【处理出入库单据】业务ID:{}", allocateId);

        String allocateCode = allocateTicket.getAllocateCode();
        logs.info("【处理出入库单据】业务单号:{}", allocateCode);

        Integer businessType = StorageBusinessType.ALLOCATE_BUSINESS.getCode();
        logs.info("【处理出入库单据】业务类型:{}", businessType);

        //库存操作参数
        StorageOperateDTO operateDTO = new StorageOperateDTO();
        List<StockDTO> dtoList = new ArrayList<>();

        storageTicket.setBusinessId(allocateId);
        storageTicket.setBusinessCode(allocateCode);
        storageTicket.setBusinessType(businessType);
        storageTicket.setStorageType(storageType);
        storageTicket.setDeletable(DeletableStatus.CAN_NOT.getCode());

        if (Objects.equals(StorageType.INTO_STORAGE.getCode(), storageType)) {
            String ticketCode = UUIDUtils.intoStorageCode();
            logs.info("【处理出入库单据】入库单据Code:{}", ticketCode);

            Long intoBelongId = allocateTicket.getIntoBelongId();
            logs.info("【处理出入库单据】入库单位:{}", intoBelongId);

            storageTicket.setTicketCode(ticketCode);
            storageTicket.setBelongId(intoBelongId);
        } else {
            String ticketCode = UUIDUtils.outStorageCode();
            logs.info("【处理出入库单据】出库据Code:{}", ticketCode);

            Long outBelongId = allocateTicket.getOutBelongId();
            logs.info("【处理出入库单据】出库单位:{}", outBelongId);

            operateDTO.setBelongId(outBelongId);
            operateDTO.setStorageType(storageType);
            operateDTO.setBusinessType(StorageBusinessType.ALLOCATE_BUSINESS.getCode());
            operateDTO.setBusinessCode(allocateCode);

            storageTicket.setOperateTime(TimeUtils.getNowDate());
            storageTicket.setOperateName(SystemName.ENABLED.getText());
            storageTicket.setTicketCode(ticketCode);
            storageTicket.setStorageStatus(StorageStatus.ALREADY_STORAGE.getCode());
            storageTicket.setBelongId(outBelongId);
        }

        logs.info("【处理出入库单据】出入库单据信息:{}", storageTicket);
        Assert.isTrue(storageTicketService.create(storageTicket), "新增失败");

        for (AllocateInfoDTO allocateInfoDTO : infoList) {
            StorageRecordsDO storageRecords = new StorageRecordsDO();

            BeanUtils.clone(allocateInfoDTO, storageRecords);
            BeanUtils.clone(storageTicket, storageRecords);

            BigDecimal allocateAmount = allocateInfoDTO.getAllocateAmount();
            logs.info("【处理出入库单据】调拨数量:{}", allocateAmount);
            storageRecords.setExpectQuantity(allocateAmount);

            if (Objects.equals(StorageType.OUT_STORAGE.getCode(), storageType)) {
                storageRecords.setActualQuantity(allocateAmount);
                storageRecords.setOperateTime(TimeUtils.getNowDate());
            }

            StockDTO stockDTO = new StockDTO();
            stockDTO.setProductId(allocateInfoDTO.getProductId());
            stockDTO.setActualQuantity(allocateAmount);

            recordsList.add(storageRecords);
            dtoList.add(stockDTO);

        }

        logs.info("【处理出入库单据】出入库记录:{}", recordsList);
        for (StorageRecordsDO storageRecords : recordsList) {
            Assert.isTrue(storageRecordsService.create(storageRecords), "新增失败");
        }

        if (Objects.equals(StorageType.OUT_STORAGE.getCode(), storageType)) {
            operateDTO.setList(dtoList);
            interactBusiness.operateStorage(operateDTO);
        }
    }

    /**
     * 处理取消状态调拨单
     */
    private void handleCancelTicket(AllocateTicketDTO dto, AllocateTicketDO allocateTicket) {
        Long allocateId = dto.getAllocateId();
        logs.info("【处理取消状态调拨单】单据ID:{}", allocateId);
        Integer allocateStatus = AllocateStatus.SCRAP.getCode();
        AllocateTicketDO update = new AllocateTicketDO();
        update.setAllocateId(allocateId);
        update.setAllocateStatus(allocateStatus);
        Assert.isTrue(allocateTicketService.update(update), "修改失败");

        allocateTicket.setAllocateId(null);
        Assert.isTrue(allocateTicketService.create(allocateTicket), "新增失败");

        List<AllocateInfoDTO> infoList = dto.getInfoList();
        logs.info("【处理取消状态调拨单】商品信息:{}", infoList);

        List<AllocateInfoDO> createList = new ArrayList<>();

        for (AllocateInfoDTO allocateInfoDTO : infoList) {
            checkAllocateInfo(allocateInfoDTO);
            AllocateInfoDO info = new AllocateInfoDO();
            BeanUtils.clone(allocateInfoDTO, info);
            //校验
            checkStock(allocateTicket, info);
            info.setInfoId(null);
            info.setAllocateId(allocateTicket.getAllocateId());
            info.setAllocateCode(allocateTicket.getAllocateCode());
            createList.add(info);
        }

        for (AllocateInfoDO create : createList) {
            Assert.isTrue(allocateInfoService.create(create), "新增失败");
        }

    }

    /**
     * 检查库存
     *
     * @param info
     */
    private void checkStock(AllocateTicketDO allocateTicket, AllocateInfoDO info) {
        Long belongId = allocateTicket.getOutBelongId();
        Long productId = info.getProductId();
        BigDecimal allocateAmount = info.getAllocateAmount();

        StockDO vo = stockService.select(new StockSelectQueryBO(belongId, productId));
        Assert.isTrue(BeanUtils.isNotNull(vo), ErrorEnum.BUSINESS_812003.getText());

        BigDecimal usableStock = vo.getUsableStock();
        logs.info("【商品库存】{}商品：报废库存{},可用库存{}", info.getProductName(), allocateAmount, usableStock);

        Assert.isTrue(!DecimalUtils.more(allocateAmount, usableStock), ErrorEnum.BUSINESS_812003.getText());
    }

    /**
     * 校验调拨单
     *
     * @param dto
     */
    private void checkAllocateTicket(AllocateTicketDTO dto) {
        Long outBelongId = dto.getOutBelongId();
        Long intoBelongId = dto.getIntoBelongId();
        List<AllocateInfoDTO> infoList = dto.getInfoList();

        logs.info("【调拨单校验】调出单位:{}", outBelongId);
        logs.info("【调拨单校验】调入单位:{}", intoBelongId);
        logs.info("【调拨单校验】调拨单详情:{}", infoList);

        Assert.isTrue(BeanUtils.isNotNull(dto.getOutBelongId()), ErrorEnum.CHECKED_811013.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getIntoBelongId()), ErrorEnum.CHECKED_811014.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getInfoList()), ErrorEnum.CHECKED_811003.getText());
        Assert.isTrue(!Objects.equals(outBelongId, intoBelongId), ErrorEnum.CHECKED_811027.getText());
    }

    /**
     * 校验调拨单详情
     *
     * @param dto
     */
    private void checkAllocateInfo(AllocateInfoDTO dto) {
        Long productId = dto.getProductId();
        String productSku = dto.getProductSku();
        String productName = dto.getProductName();
        BigDecimal allocateAmount = dto.getAllocateAmount();
        Long unitId = dto.getUnitId();

        logs.info("【商品参数校验】商品ID:{}", productId);
        logs.info("【商品参数校验】商品sku:{}", productSku);
        logs.info("【商品参数校验】商品名称:{}", productName);
        logs.info("【商品参数校验】调拨数量:{}", allocateAmount);
        logs.info("【商品参数校验】商品单位:{}", unitId);

        Assert.isTrue(BeanUtils.isNotNull(dto.getProductId()), ErrorEnum.CHECKED_811003.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getProductSku()), ErrorEnum.CHECKED_811003.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getProductName()), ErrorEnum.CHECKED_811003.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getAllocateAmount()), ErrorEnum.CHECKED_811011.getText());
        Assert.isTrue(BeanUtils.isNotNull(dto.getUnitId()), ErrorEnum.CHECKED_811003.getText());
        Assert.isTrue(DecimalUtils.more(allocateAmount, BigDecimal.ZERO), ErrorEnum.CHECKED_811012.getText());
    }

}